﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xunit;
using NSubstitute;
using System.Threading.Tasks;

namespace MetaSharp.Transformation.Tests.Patterns
{
    public class TraceTests
    {
        [Fact]
        public async Task CanTraceSingleItem()
        {
            var p = Pattern.Rule(Pattern.Value('a'));
            var m = await Pattern.Match(p, "a");
            var trace = m.Trace();
            Assert.Equal(SourceSpan.From(0, 1), trace);
        }

        [Fact]
        public async Task NonRulePatternsAreUntraceable()
        {
            var p = Pattern.Any();
            var m = await Pattern.Match(p, "a");
            var trace = m.Trace();
            Assert.Null(trace);
        }


        [Fact]
        public async Task CanTraceLines()
        {
            var p = Pattern.Rule(Pattern.OneOrMore(Pattern.Rule(Pattern.Any())));
            var m = await Pattern.Match(p, "a\nb\nc");
            var trace = m.Trace();
            Assert.Equal(SourceSpan.From(0, 5), trace);
        }

        [Fact]
        public async Task CanTraceInnerValue()
        {
            var p = Pattern.Rule(Pattern.OneOrMore(Pattern.Rule(Pattern.Any())));
            var m = await Pattern.Match(p, "abc");
            var v = (object[])m.Value;
            var trace = m.End.Memos.Trace(v[1]);
            Assert.Equal(SourceSpan.From(1, 2), trace);
        }

        [Fact]
        public async Task CanTraceProduction()
        {
            var expected = new object();
            var p = Pattern.Rule(Pattern.Production(Pattern.Value('a'), () => expected));
            var m = await Pattern.Match(p, "a");
            var trace = m.End.Memos.Trace(expected);
            Assert.Equal(SourceSpan.From(0, 1), trace);
        }

        [Fact]
        public async Task NestedRulesTraceSuccessfully()
        {
            var a = Pattern.Rule(Pattern.Value('a'));
            var b = Pattern.Rule(Pattern.Value('b'));
            var p = Pattern.Rule(Pattern.And(a, b));
            var m = await Pattern.Match(p, "ab");
            var trace = m.Trace();
            Assert.Equal(SourceSpan.From(0, 2), trace);
        }

        [Fact]
        public async Task TracesSingleAcrossMultipleSteps()
        {
            var p1 = Pattern.Rule(Pattern.Any());
            var p2 = Pattern.Rule(Pattern.Any());
            var p = Pattern.Transform(p1, p2);
            var m = await Pattern.Match(p, new { });
            var trace = m.Trace();
            Assert.Equal(SourceSpan.From(0, 1), trace);
        }

        [Fact]
        public async Task TracesMultipleAcrossMultipleSteps()
        {
            var r = Pattern.Rule(Pattern.OneOrMore(Pattern.Rule(Pattern.Any())));
            var p = Pattern.Transform(r, r, r);
            var m = await Pattern.Match(p, "abcd");
            var trace = m.Trace();
            Assert.Equal(SourceSpan.From(0, 4), trace);
        }

        [Fact]
        public async Task CanTraceAcrossComplexObjects()
        {
            var expected = new object();
            var obj = new { test = 7 };

            var p2 = Pattern.Rule(Pattern.Value(7));
            var p1 = Pattern.Rule(
                Pattern.Production(
                    Pattern.Object(
                        typeof(object),
                        Pattern.Property("test", Pattern.Variable("t", p2))),
                    (int t) => expected));

            var m = await Pattern.Match(p1, new[] { obj });
            var trace = m.Trace();
            Assert.Equal(SourceSpan.From(0, 1), trace);
        }

        [Fact]
        public async Task CanTraceAcross3Transforms()
        {
            var o1 = new object();
            var o2 = new object();
            var o3 = new object();

            var p1 = Pattern.Rule(Pattern.Production(Pattern.Any(), () => o1));
            var p2 = Pattern.Rule(Pattern.Production(Pattern.Any(), () => o2));
            var p3 = Pattern.Rule(Pattern.Production(Pattern.Any(), () => o3));
            var p = Pattern.Transform(p1, p2, p3);
            var m = await Pattern.Match(p, new { });
            var trace = m.Trace();
            Assert.Equal(SourceSpan.From(0, 1), trace);
        }
    }
}
